package server.communication;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;

import server.communication.MessageProcessor.ClientType;

public class Server extends Thread{

	private ServerSocket servSocket;
	private int port;
	LinkedList <Client>clients;
	private static Server instance = null;
	private Semaphore clientsSem = new Semaphore(1);
	private RootThread root;
	
	private Server(int port)
	{
		this.port = port;
	}
	
	public static Server getInstance(int port)
	{
		if (instance == null)
		{
			instance = new Server(port);
			return instance;
		}
		return null;
	}
	
	public static Server getInstance()
	{
		return instance;
	}
	public void startServer()
	{
		try
		{
			servSocket = new ServerSocket(port);
			System.out.println("Server started");
		}
		catch (Exception e)
		{
			System.err.println("Error occured while starting server");
			return;
		}
		clients = new LinkedList<Client>();
		start();
	}
	
	protected void listen()
	{
		System.out.println("System listening at the port " + port);
		while (true)
		{
			try
			{
				Socket clientSock = servSocket.accept();
				if (MessageProcessor.getClientType(clientSock) == ClientType.user)
				{
					Client client = new Client(clientSock);
					//client.askAboutClientDetails();
					addClient(client);
					System.out.println("New user connected");
				}
				else
				{
					if (root != null && root.isAlive())
					{
						root.closeConnection();
						root.join(500);
						if (root.isAlive())
							root.stop();
					}
					root = new RootThread(clientSock);
					root.start();
					System.out.println("Root connected!!! Die motherfuckers! :D");
				}
			}
			catch (IOException e)
			{
				System.err.println("IOException occured while accepting a new client");
			}
			catch (Exception e)
			{
				System.err.println("Error occured while accepting a new client: " + e);
			}
		}
	}
	
	public int countClients()
	{
		return clients.size();
	}
	
	@Override
	public void run()
	{
		listen();
	}
	
	public void reset()
	{
		stopServer();
		startServer();
	}
	
	public void stopServer()
	{
		try
		{
			servSocket.close();
		}
		catch(Exception e){}
		for (Client c : clients)
			c.stop();
		clients.clear();
		this.stop();
		if (clientsSem.availablePermits() == 0)
			clientsSem.release();
	}
	@Deprecated
	public void returnClients()
	{
		if (clientsSem.availablePermits() == 0)
			clientsSem.release();
	}
	@Deprecated
	public LinkedList<Client> getClients()
	{
		try
		{
			clientsSem.acquire();
		}
		catch(Exception e)
		{
			System.err.println("Unable to acquire clients' list");
			return null;
		}
		return clients;
	}
	
	public void removeClient(Client c)
	{
		try
		{
			clientsSem.acquire();
		}
		catch(Exception e)
		{
			System.err.println("Unable to acquire clients' list");
		}
		clients.remove(c);
		clientsSem.release();
	}
	
	public void addClient(Client c)
	{
		try
		{
			clientsSem.acquire();
		}
		catch(Exception e)
		{
			System.err.println("Unable to acquire clients' list");
		}
		clients.add(c);
		clientsSem.release();
	}
	
	public boolean executeOnClient(String name, String address, String command)
	{
		LinkedList<Client> list = getClients();
		boolean test=false;
		for (Client c : list)
		{
			if ((name != null && c.getHostName().equals(name)) || (address != null && c.getHostAddress().equals(address)))
			{
				c.executeCommand(command, root.getSocket());
				test = true;
				break;
			}
		}
		returnClients();
		return test;
	}
	
	public boolean executeBashOnClient(String name, String address, String command)
	{
		LinkedList<Client> list = getClients();
		boolean test=false;
		for (Client c : list)
		{
			if ((name != null && c.getHostName().equals(name)) || (address != null && c.getHostAddress().equals(address)))
			{
				c.executeBashCommand(command, root.getSocket());
				test = true;
				break;
			}
		}
		returnClients();
		return test;
	}
}
